package com.cyw.common.utils.http;

import com.cyw.common.exceptions.BaseException;
import com.cyw.common.utils.DateUtils;
import com.cyw.common.utils.JsonUtils;
import com.cyw.common.utils.LogUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * apache httpclient工具的简单封装
 * 主要功能有下面两个：
 * 1、执行http请求;
 * 2、获取HttpClient对象;
 *
 * @author sxw
 * @date 2018-11-23
 */
public class HttpClientUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger("thirdpart_log");

    private static final int POOL_MAX_TOTAL = 100;
    private static final int CONNECT_TIMEOUT = 8000;
    private static final int SOCKET_TIMEOUT = 30000;

    /**
     * 默认HTTP请求器
     */
    private static final CloseableHttpClient DEFAULT_HTTP_CLIENT;

    static {
        DEFAULT_HTTP_CLIENT = HttpClientBuilder.create()
                .withPoolMaxTotal(POOL_MAX_TOTAL).withConnectTimeout(CONNECT_TIMEOUT)
                .withSocketTimeout(SOCKET_TIMEOUT).withSecure(false).withRedirectEnable(false)
                .build();
    }

    /**
     * 发送http请求,并获取响应的字符串
     * @param request
     * @return
     */
    public static String execute(HttpRequest request) {
        return doGetEntityString(executeNoParse(request));
    }

    /**
     * 发送http请求，并将返回JSON字符串反序列化为指定类型的对象
     * @param clazz
     * @param request
     * @param <T>
     * @return
     */
    public static <T> T execute(Class<T> clazz, HttpRequest request) {
        return JsonUtils.toObject(execute(request), clazz);
    }

    /**
     * 发送http请求，获取原始的响应结果*
     * @param request 请求对象
     * @return        包装后的响应对象
     * @see HttpResponseWrapper#abort()
     * @see #doService(CloseableHttpClient, HttpRequestBase, HttpContext)
     */
    public static HttpResponseWrapper executeNoParse(HttpRequest request) {

        LOGGER.info("http request object:{}", request.toString());
        HttpContext httpContext = request.getHttpContext();
        String url = request.getUrl();
        CloseableHttpClient client = request.getClient();
        HttpRequest.HttpMethod httpMethod = request.getHttpMethod();
        Map<String, Object> params = request.getParams();
        Map<String, String> headerMap = request.getHeaderMap();

        switch (httpMethod) {
            case GET:
                return executeGetNoParse(url, params, headerMap, client, httpContext);
            case POST:
                return executePostNoParse(url, params, headerMap, client, httpContext);
            case POST_JSON:
                return executePostJsonNoParse(url, JsonUtils.toJson(params), headerMap, client, httpContext);
            case POST_ENTITY:
                return executePostEntityNoParse(url, request.getEntity(), headerMap, client, httpContext);
            case PUT:
                return executePutNoParse(url, params, headerMap, client, httpContext);
            case DELETE:
                return executeDeleteNoParse(url, params, headerMap, client, httpContext);
        }
        return new HttpResponseWrapper(null, new BaseException("不支持的请求"));
    }

    /**
     * 发送post请求
     *
     * @param url    API地址
     * @return API回包的实际数据
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executePost(String url, Map<String, Object> params) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST)
                        .withParams(params)
        );
    }

    /**
     * 使用自定义httpclient发送post请求
     * @param url
     * @param params
     * @param client
     * @return
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executePost(String url, Map<String, Object> params, CloseableHttpClient client) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST)
                        .withParams(params)
                        .withClient(client)
        );
    }

    /**
     * 使用自定义httpclient发送带header的post请求
     * @param url
     * @param params
     * @param headerMap
     * @param client
     * @return
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executePost(String url, Map<String, Object> params, Map<String, String> headerMap, CloseableHttpClient client) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST)
                        .withParams(params)
                        .withHeaderMap(headerMap)
                        .withClient(client)
        );
    }

    /**
     * 执行post请求，获取原始HttpResponse对象
     * @param url
     * @param params
     * @param headerMap
     * @param client
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper executePostNoParse(
            String url, Map<String, Object> params, Map<String, String> headerMap,
            CloseableHttpClient client, HttpContext httpContext
    ) {
        HttpPost httpPost = new HttpPost(url);

        List<BasicNameValuePair> nvs = getNameValuePairs(params);
        StringEntity postEntity = null;
        try {
            postEntity = new UrlEncodedFormEntity(nvs, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtils.printWarn(e);
            return new HttpResponseWrapper(null, e);
        }

        addHeader(headerMap, httpPost);

        httpPost.setEntity(postEntity);

        return doService(client, httpPost, httpContext);
    }

    /**
     * 上传文件(字节数组)
     * @param url       上传地址
     * @param params    文本参数和字节数组参数
     * @param fileNames 须和params中的字节数组参数一一对应
     * @param client
     * @return
     * @see #execute(HttpRequest)
     * @see HttpRequest.HttpMethod#POST_ENTITY
     */
    public static String executePostFormData(String url, Map<String, Object> params, Map<String, String> fileNames, CloseableHttpClient client) {
        HttpEntity entity = buildMultiPartEntity(params, fileNames);
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST_ENTITY)
                        .withClient(client)
                        .withEntity(entity)
        );
    }

    /**
     * 执行get请求
     * @param url
     * @param params
     * @return
     * @throws IOException
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executeGet(String url, Map<String, Object> params) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.GET)
                        .withParams(params));
    }

    /**
     * 使用自定义httpclient执行get请求
     * @param url
     * @param params
     * @param client
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executeGet(String url, Map<String, Object> params, CloseableHttpClient client) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.GET)
                        .withParams(params)
                        .withClient(client)
        );
    }

    /**
     * 使用自定义httpclient执行带header的get请求
     * @param url
     * @param params
     * @param headerMap
     * @param client
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executeGet(String url, Map<String, Object> params, Map<String, String> headerMap, CloseableHttpClient client) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.GET)
                        .withParams(params)
                        .withClient(client)
                        .withHeaderMap(headerMap)
        );
    }

    /**
     * 执行请求，获取原始HttpResponse对象
     * @param url
     * @param params
     * @param headerMap
     * @param client
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper executeGetNoParse(
            String url, Map<String, Object> params, Map<String, String> headerMap,
            CloseableHttpClient client, HttpContext httpContext
    ) {
        url = buildUrlWithQueryParams(url, params);

        LOGGER.debug("url:{}", url);

        HttpGet httpGet = new HttpGet(url);
        addHeader(headerMap, httpGet);
        return doService(client, httpGet, httpContext);
    }

    /**
     * 下载文件
     * @param url
     * @return
     */
    public static byte[] executeGetAndDownload(String url) {
        HttpGet httpGet = new HttpGet(url);
        HttpResponse response = null;
        try {
            response = DEFAULT_HTTP_CLIENT.execute(httpGet);
            HttpEntity responseEntity = response.getEntity();
            InputStream content = responseEntity.getContent();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(content, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            LogUtils.printWarn(e);
        }

        return new byte[0];
    }

    /**
     * 执行带header的put请求,获取原始HttpResponse对象
     * @param url
     * @param params
     * @param headerMap
     * @param client
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper executePutNoParse(
            String url, Map<String, Object> params, Map<String, String> headerMap,
            CloseableHttpClient client, HttpContext httpContext
    ) {
        HttpPut httpPut = new HttpPut(url);

        List<BasicNameValuePair> nvs = getNameValuePairs(params);

        StringEntity entity = null;
        Exception ex = null;
        try {
            entity = new UrlEncodedFormEntity(nvs, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtils.printWarn(e);
            ex = e;
        }

        if (ex != null) {
            return new HttpResponseWrapper(null, ex);
        }

        addHeader(headerMap, httpPut);
        httpPut.setEntity(entity);

        return doService(client, httpPut, httpContext);
    }

    /**
     * 执行带header的delete请求,获取原始HttpResponse对象
     * @param url
     * @param params
     * @param headerMap
     * @param client
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper executeDeleteNoParse(
            String url, Map<String, Object> params, Map<String, String> headerMap,
            CloseableHttpClient client, HttpContext httpContext
    ) {
        url = buildUrlWithQueryParams(url, params);

        LOGGER.debug("url:{}", url);

        HttpDelete httpDelete = new HttpDelete(url);
        addHeader(headerMap, httpDelete);

        return doService(client, httpDelete, httpContext);
    }

    /**
     * 通过Https往API post json数据
     *
     * @param url    API地址
     * @return API回包的实际数据
     * @throws IOException
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executePostJson(String url, Map<String, Object> jsonParam) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST_JSON)
                        .withParams(jsonParam)
        );
    }

    /**
     * 通过Https往API post json数据
     *
     * @param url    API地址
     * @return API回包的实际数据
     * @throws IOException
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executePostJson(String url, Map<String, Object> jsonParam, CloseableHttpClient client) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST_JSON)
                        .withParams(jsonParam)
                        .withClient(client)
        );
    }

    /**
     * 发送post请求，实体数据为json
     *
     * @param url    API地址
     * @return API回包的实际数据
     * @see #execute(HttpRequest)
     * @see #executeNoParse(HttpRequest)
     */
    public static String executePostJson(String url, Map<String, Object> jsonParam, Map<String, String> headerMap, CloseableHttpClient client) {
        return execute(
                HttpRequest.newHttpRequest(url)
                        .withHttpMethod(HttpRequest.HttpMethod.POST_JSON)
                        .withParams(jsonParam)
                        .withClient(client)
                        .withHeaderMap(headerMap)
        );
    }

    /**
     * 执行post json请求，获取原始HttpResponse对象
     * @param url
     * @param json
     * @param headerMap
     * @param client
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper executePostJsonNoParse(
            String url, String json, Map<String, String> headerMap,
            CloseableHttpClient client, HttpContext httpContext
    ) {
        StringEntity postEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
        LOGGER.debug("{}接口发送json数据:{}, header:{}",url, json, headerMap);
        return executePostEntityNoParse(url, postEntity, headerMap, client, httpContext);
    }

    /**
     * post实体请求
     * @param url
     * @param entity
     * @param headerMap
     * @param client
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper executePostEntityNoParse(
            String url, HttpEntity entity, Map<String, String> headerMap,
            CloseableHttpClient client, HttpContext httpContext
    ) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(entity);
        addHeader(headerMap, httpPost);
        return doService(client, httpPost, httpContext);
    }

    /**
     * 设置请求header
     * @param headerMap
     * @param requestBase
     */
    private static void addHeader(Map<String, String> headerMap, HttpRequestBase requestBase) {
        if (headerMap != null) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                requestBase.addHeader(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * 组装nameValuePair
     * @param params
     * @return
     */
    private static List<BasicNameValuePair> getNameValuePairs(Map<String, Object> params) {
        List<BasicNameValuePair> nvs = new ArrayList<>();
        if (params != null) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                Object value = entry.getValue();
                BasicNameValuePair nv = new BasicNameValuePair(entry.getKey(), value != null ? value.toString() : "");
                nvs.add(nv);
            }
        }
        return nvs;
    }

    /**
     * url拼接查询字符串
     * @param url
     * @param params
     * @return
     */
    private static String buildUrlWithQueryParams(String url, Map<String, Object> params) {
        List<BasicNameValuePair> nvs = getNameValuePairs(params);
        String formatParams = URLEncodedUtils.format(nvs, "UTF-8");
        if (StringUtils.isNotBlank(formatParams)) {
            url += (url.contains("?") ? "&" : "?") + formatParams;
        }
        return url;
    }

    /**
     * 请求执行与响应的简单处理
     * @param client
     * @param requestBase
     * @param httpContext
     * @return
     */
    private static HttpResponseWrapper doService(
            CloseableHttpClient client, HttpRequestBase requestBase, HttpContext httpContext
    ) {
        HttpResponseWrapper responseWrapper = new HttpResponseWrapper();
        responseWrapper.setRequestBase(requestBase);
        HttpResponse response = null;
        try {
            CloseableHttpClient httpClient = client != null ? client : DEFAULT_HTTP_CLIENT;
            Date startTime = new Date();
            response = httpClient.execute(requestBase, httpContext);
            responseWrapper.setHttpResponse(response);

            boolean hasResponse = response != null;
            Integer statusCode = hasResponse ? response.getStatusLine().getStatusCode() : null;

            String result = null;
            if (hasResponse) {
                // 确保读取了数据，并缓存起来
                String entityAsString = responseWrapper.getEntityAsString();
                Header contentTypeHeader = response.getFirstHeader("content-type");
                boolean isHtml = Objects.nonNull(contentTypeHeader)
                        && Objects.nonNull(contentTypeHeader.getValue())
                        && contentTypeHeader.getValue().contains("html");
                if (isHtml) {
                    result = "html document, length:" + (entityAsString != null ? entityAsString.length() : 0);
                } else {
                    result = entityAsString;
                }
            }

            LOGGER.info("uri:{}, started at:{}, finished now, statusCode:{}, cost:{}ms, result:{}",
                    requestBase.getURI(),
                    DateUtils.formatDate(startTime, DateUtils.DEFAULT_PATTERN),
                    statusCode,
                    System.currentTimeMillis() - startTime.getTime(),
                    result
            );

        } catch (Exception e) {
            LogUtils.printWarn(e, "http execute error");

            if (response != null) {
                LOGGER.warn("httprequest result:{}", responseWrapper.getEntityAsString());
                EntityUtils.consumeQuietly(response.getEntity());
            }
            responseWrapper.setHttpResponse(null);
            responseWrapper.setException(e);
        } finally {
            /**
             * 确保已经读取并存储了数据，否则调用者可能获取不到数据，{@link HttpResponseWrapper#getEntityAsString()}
             */
            responseWrapper.abort();
        }
        return responseWrapper;
    }

    private static String doGetEntityString(HttpResponseWrapper responseWrapper) {
        HttpRequestBase requestBase = responseWrapper.getRequestBase();
        try {
            if (responseWrapper.getHttpResponse() == null || responseWrapper.getException() != null) {
                LOGGER.warn("http execute fail");
                return "";
            }

            HttpResponse response = responseWrapper.getHttpResponse();

            /**
             * 处理被调用接口是否有返回重定向url.
             */
            Header location = response.getFirstHeader("Location");
            if (location != null) {
                LOGGER.debug("http response header location:{}", location.getValue());
            }
            return responseWrapper.getEntityAsString();
        } finally {
            requestBase.abort();
        }
    }

    /**
     * 构造带文件和参数的请求实体
     * @param params
     * @param fileNames
     * @return
     */
    public static HttpEntity buildMultiPartEntity(Map<String, Object> params, Map<String, String> fileNames) {
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value == null) {
                LOGGER.debug("参数为空，跳过");
                continue;
            }

            if (value instanceof byte[]) {
                LOGGER.trace("文件参数");
                builder.addBinaryBody(key, (byte[]) value, ContentType.MULTIPART_FORM_DATA, fileNames.get(key));
            } else {
                LOGGER.trace("文本参数");
                builder.addTextBody(key, value.toString(), ContentType.MULTIPART_FORM_DATA);
            }
        }
        return builder.build();
    }

    /**
     * 信任所有站点（无视https证书的https连接）, 支持http
     * @return
     */
    public static synchronized CloseableHttpClient getHttpClientWithSSL() {
        return getHttpClientWithSSL(POOL_MAX_TOTAL, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
    }

    /**
     * 信任所有站点（无视https证书的https连接）
     * @param poolMaxTotal
     * @param connectTimeout
     * @param socketTimeout
     * @return
     */
    public static synchronized CloseableHttpClient getHttpClientWithSSL(
            int poolMaxTotal, int connectTimeout, int socketTimeout) {
        return HttpClientBuilder.create()
                .withPoolMaxTotal(poolMaxTotal)
                .withConnectTimeout(connectTimeout)
                .withSocketTimeout(socketTimeout)
                .withSecure(false)
                .withRedirectEnable(true)
                .build();
    }

    /**
     * 只信任安全站点(使用该方法可能需要服务器导入证书)
     *
     * @param poolMaxTotal
     * @param connectTimeout
     * @param socketTimeout
     * @return
     */
    public static synchronized CloseableHttpClient getSecureHttpClient(
            int poolMaxTotal, int connectTimeout, int socketTimeout) {
        return HttpClientBuilder.create()
                .withPoolMaxTotal(poolMaxTotal)
                .withConnectTimeout(connectTimeout)
                .withSocketTimeout(socketTimeout)
                .withSecure(true)
                .withRedirectEnable(true)
                .build();
    }

    /**
     * 信任安全站点，且使用指定代理访问
     *
     * @param poolMaxTotal
     * @param connectTimeout
     * @param socketTimeout
     * @param proxy          代理主机
     * @param socksProxy     proxy是否是socks代理
     * @return
     */
    public static synchronized CloseableHttpClient getSecureHttpClientWithProxy(
            int poolMaxTotal, int connectTimeout, int socketTimeout, HttpHost proxy, boolean socksProxy
    ) {
        return HttpClientBuilder.create()
                .withPoolMaxTotal(poolMaxTotal)
                .withConnectTimeout(connectTimeout)
                .withSocketTimeout(socketTimeout)
                .withSecure(true).withProxy(proxy)
                .withSocksProxy(socksProxy)
                .withRedirectEnable(true)
                .build();
    }

    public static CloseableHttpClient getDefaultHttpClient() {
        return DEFAULT_HTTP_CLIENT;
    }
}
